package tools

import (
	"errors"
	"golang.org/x/net/ipv4"
	"log"
	"net"
	"os"
	"ospf/common"
	"ospf/msgs"
	"time"
)

// ospf multicast address is usually 224.0.0.5 or 224.0.0.6
var (
	// AllSPFRouters is the IPv4 multicast group address that all routers
	// running OSPF should participate in.
	AllSPFRouters = &net.IPAddr{
		IP: net.IP{224, 0, 0, 5},
	}

	// AllDRouters is the IPv4 multicast group address that the Designated
	// Router and Backup Designated Router running OSPF must participate in.
	AllDRouters = &net.IPAddr{
		IP: net.IP{224, 0, 0, 6},
	}
)

type Conn struct {
	conn   *ipv4.PacketConn
	inf    *net.Interface
	groups []*net.IPAddr
}

func (c *Conn) GetInf() *net.Interface {
	return c.inf
}

// Close closes the Conn's underlying network connection.
func (c *Conn) Close() error {
	for _, g := range c.groups {
		if err := c.conn.LeaveGroup(c.inf, g); err != nil {
			return err
		}
	}

	return c.conn.Close()
}

// Write writes package to a Conn
func (c *Conn) Write(p msgs.Packet, dst *net.IPAddr) error {
	b, err := p.Serialize()
	if err != nil {
		log.Printf("Error serializing packet: %s", err)
	}
	_, err = c.conn.WriteTo(b, nil, dst)
	return err
}

// Read reads package from a Conn
func (c *Conn) Read() (msgs.Packet, *ipv4.ControlMessage, *net.IPAddr, error) {
	b := make([]byte, c.inf.MTU)
	for {
		n, cm, src, err := c.conn.ReadFrom(b)
		if err != nil {
			return nil, nil, nil, err
		}
		p, err := msgs.DeserializePacket(b[:n])
		if err != nil {
			// Assume invalid ospf data, keep reading.
			// Delete
			log.Printf("Error deserializing packet: %s\n", err)
			continue
		}
		return p, cm, src.(*net.IPAddr), nil
	}
}

func (c *Conn) BroadcastPacket(p msgs.Packet) error {
	err := c.Write(p, AllSPFRouters)
	if err != nil {
		return common.ErrSendPacket("broadcastPacket " + err.Error())
	}
	return nil
}

func BuildConn(ifNames []string) []*Conn {
	var connections []*Conn

	var infs []*net.Interface
	for _, ifName := range ifNames {
		inf, err := net.InterfaceByName(ifName)
		if err != nil {
			var err1 *net.OpError
			if errors.As(err, &err1) && err1.Err.Error() == "no such network interface" {
				log.Printf("skipping, interface %v does not exist", ifName)
				continue
			}
			log.Fatalf("failed to get interface %v: %v", ifName, err)
		}
		infs = append(infs, inf)
	}

	// check if all the interfaces are usable.
	for _, inf := range infs {
		waitInfReady(inf, 5)
	}

	// start listening at all the interfaces.
	for _, inf := range infs {
		con, err := Listen(inf)
		if err != nil {
			if errors.Is(err, os.ErrPermission) {
				log.Printf("skipping, permission denied while trying to listen on interface %v", inf.Name)
				continue
			}
			log.Fatalf("failed to listen on interface %v: %v", inf.Name, err)
		}
		log.Printf("listening on interface %v", inf.Name)
		connections = append(connections, con)
	}
	return connections
}

func waitInfReady(inf *net.Interface, timeout int) {
	for i := 0; i < timeout; i++ {
		if i > 0 {
			time.Sleep(1 * time.Second)
			log.Printf("waiting for %v readiness...", inf.Name)
		}

		addrs, err := inf.Addrs()
		if err != nil {
			log.Fatalf("failed to get first addresses: %v", err)
		}

		if len(addrs) == 0 {
			// No addresses yet.
			continue
		}
		return
	}

	log.Fatal("failed to wait for interface readiness")
}

// Listen creates a *Conn using the specified network interface.
func Listen(inf *net.Interface) (*Conn, error) {
	// IP protocol number 89 is OSPF.
	conn, err := net.ListenPacket("ip4:89", "0.0.0.0")
	if err != nil {
		return nil, err
	}
	c := ipv4.NewPacketConn(conn)

	// Return all possible control message information to the caller, so they
	// can make more informed choices.
	if err := c.SetControlMessage(^ipv4.ControlFlags(0), true); err != nil {
		return nil, err
	}

	// Join the appropriate multicast groups. Note that point-to-point links
	// don't use DR/BDR and can skip joining that group.
	if err := c.SetMulticastInterface(inf); err != nil {
		return nil, err
	}

	groups := []*net.IPAddr{AllSPFRouters}
	if inf.Flags&net.FlagPointToPoint == 0 {
		groups = append(groups, AllDRouters)
	}

	for _, g := range groups {
		if err := c.JoinGroup(inf, g); err != nil {
			return nil, err
		}
	}

	// Don't read our own multicast packets during concurrent read/write.
	if err := c.SetMulticastLoopback(false); err != nil {
		return nil, err
	}

	return &Conn{
		conn:   c,
		inf:    inf,
		groups: groups,
	}, nil
}
